001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.library.console;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.net.URI;
024    import java.util.ArrayList;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Hashtable;
028    
029    import net.dpml.util.Logger;
030    
031    import net.dpml.lang.Part;
032    
033    import net.dpml.library.Module;
034    import net.dpml.library.Resource;
035    import net.dpml.library.Builder;
036    import net.dpml.library.Type;
037    import net.dpml.library.info.ResourceDirective.Classifier;
038    import net.dpml.library.info.Scope;
039    import net.dpml.library.impl.DefaultLibrary;
040    
041    import net.dpml.cli.Option;
042    import net.dpml.cli.Group;
043    import net.dpml.cli.CommandLine;
044    import net.dpml.cli.commandline.Parser;
045    import net.dpml.cli.util.HelpFormatter;
046    import net.dpml.cli.OptionException;
047    import net.dpml.cli.DisplaySetting;
048    import net.dpml.cli.builder.ArgumentBuilder;
049    import net.dpml.cli.builder.GroupBuilder;
050    import net.dpml.cli.builder.DefaultOptionBuilder;
051    import net.dpml.cli.builder.CommandBuilder;
052    import net.dpml.cli.option.PropertyOption;
053    import net.dpml.cli.validation.URIValidator;
054    
055    /**
056     * Plugin that handles multi-project builds based on supplied commandline arguments.
057     *
058     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
059     * @version 1.1.0
060     */
061    public class BuilderPlugin
062    {
063        // ------------------------------------------------------------------------
064        // static
065        // ------------------------------------------------------------------------
066    
067        private static final URI ANT_BUILDER_URI;
068        
069        // ------------------------------------------------------------------------
070        // state
071        // ------------------------------------------------------------------------
072        
073        private final Logger m_logger;
074        private final DefaultLibrary m_library;
075        private final Map m_map = new Hashtable();
076        
077        private boolean m_verbose;
078        private boolean m_expand = false;
079        
080        // ------------------------------------------------------------------------
081        // constructors
082        // ------------------------------------------------------------------------
083    
084       /**
085        * Builder establishment. System property have already been assigned
086        * to the current jvm by depot.  
087        *
088        * @param logger the assigned logging channel
089        * @param args supplimentary command line arguments
090        * @exception Exception if the build fails
091        */
092        public BuilderPlugin( Logger logger, String[] args )
093            throws Exception
094        {
095            m_logger = logger;
096            
097            Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
098            
099            CommandLine line = getCommandLine( args );
100            m_verbose = line.hasOption( VERBOSE_OPTION );
101            
102            // setup the build version
103            
104            if( line.hasOption( VERSION_OPTION ) )
105            {
106                String version = (String) line.getValue( VERSION_OPTION, "SNAPSHOT" );
107                System.setProperty( "build.signature", version );
108                if( m_verbose )
109                {
110                    getLogger().info( "Setting version to: " + version );
111                }
112            }
113            
114            if( line.hasOption( DECIMAL_OPTION ) )
115            {
116                System.setProperty( Resource.DECIMAL_VERSIONING_KEY, "true" );
117                if( m_verbose )
118                {
119                    getLogger().info( "Enabling decimal versioning." );
120                }
121            }
122            
123            m_library = new DefaultLibrary( logger );
124            
125            try
126            {
127                if( line.hasOption( HELP_OPTION ) )
128                {
129                    processHelp();
130                    System.exit( 0 );
131                }
132                else
133                {
134                    if( line.hasOption( LIST_OPTION ) )
135                    {
136                        m_expand = line.hasOption( EXPAND_OPTION );
137                        Resource[] resources = getTargetSelection( line );
138                        if( resources.length == 0 )
139                        {
140                            getLogger().info( "Empty selection." );
141                            System.exit( 0 );
142                        }
143                        else
144                        {
145                            list( resources );
146                        }
147                    }
148                    else
149                    {
150                        Resource[] resources = getSelection( line );
151                        if( resources.length == 0 )
152                        {
153                            getLogger().info( "Empty selection." );
154                            System.exit( 0 );
155                        }
156                        boolean result = process( line, resources );
157                        if( !result )
158                        {
159                            System.exit( 1 );
160                        }
161                        else
162                        {
163                            System.exit( 0 );
164                        }
165                    }
166                }
167            }
168            catch( OptionException e )
169            {
170                m_logger.error( e.getMessage() );
171            }
172        }
173        
174        private CommandLine getCommandLine( String[] args ) throws Exception
175        {
176            Parser parser = new Parser();
177            parser.setGroup( COMMAND_GROUP );
178            return parser.parse( args );
179        }
180            
181        private Part createPart( URI uri ) throws Exception
182        {
183            try
184            {
185                Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
186                return Part.load( uri );
187            }
188            catch( Exception e )
189            {
190                final String error = 
191                  "Unexpected error occured while attempting to load builder.\nURI: "
192                  + uri;
193                throw new BuilderError( error, e );
194            }
195        }
196        
197        private Builder createBuilder( Part part ) throws Exception
198        {
199            Object[] params = new Object[]{m_logger, m_library, new Boolean( m_verbose )};
200            return (Builder) part.instantiate( params );
201        }
202        
203       /**
204        * Resolve the project selection taking into account any overriding -s 
205        * selection option, the -c switch, or in the absence of a selction, the 
206        * implicit slection relative to the current working directory.
207        *
208        * @param line the commandline
209        * @return the resolved array of resources sorted relative to build sequence
210        */
211        private Resource[] getSelection( CommandLine line ) throws Exception
212        {
213            ArrayList list = new ArrayList();
214            Resource[] targets = getTargetSelection( line );
215            for( int i=0; i<targets.length; i++ )
216            {
217                Resource project = targets[i];
218                if( Classifier.LOCAL.equals( project.getClassifier() ) )
219                {
220                    list.add( project );
221                }
222            }
223            return (Resource[]) list.toArray( new Resource[ list.size() ] );
224        }
225        
226       /**
227        * Get the base selection and check if the consumer switch is present and 
228        * if so build the consumers list from the selection list.
229        * 
230        * @param line the commandline
231        * @return the array of projects in build order
232        */
233        private Resource[] getTargetSelection( CommandLine line ) throws Exception
234        {
235            Resource[] resources = getBaseSelection( line );
236            if( resources.length == 0 )
237            {
238                return resources;
239            }
240            boolean flag = line.hasOption( CONSUMERS_OPTION );
241            if( flag )
242            {
243                if( resources.length != 1 )
244                {
245                    final String error = 
246                      "Consumer resolution against a multi-element selection is not supported.";
247                    getLogger().error( error );
248                    return new Resource[0];
249                }
250                else
251                {
252                    Resource resource = resources[0];
253                    return resource.getConsumers( true, true );
254                }
255            }
256            else
257            {
258                return resources;
259            }
260        }
261        
262       /**
263        * Get the set of projects taking into consideration either the 
264        * overriding selection option or the base directory if no selection specificed.
265        * 
266        * @param line the commandline
267        * @return the array of projects in build order
268        */
269        private Resource[] getBaseSelection( CommandLine line ) throws Exception
270        {
271            String selection = (String) line.getValue( SELECT_OPTION, null );
272            if( null != selection )
273            {
274                getLogger().debug( "parsing selection: " + selection );
275                Resource[] resources = m_library.select( selection, false, true );
276                getLogger().debug( "selection: " + resources.length );
277                return resources;
278            }
279            else
280            {
281                String work = System.getProperty( "user.dir" );
282                getLogger().debug( "resolving selection in: " + work );
283                File file = new File( work ).getCanonicalFile();
284                Resource[] resources = m_library.select( file );
285                return resources;
286            }
287        }
288    
289       /**
290        * Build the supplied set of projects. If a build filure occurs then 
291        * abort the build sequence and exit.
292        *
293        * @param line the commandline
294        * @param resources the sorted sequence of projects to build
295        */
296        private boolean process( CommandLine line, Resource[] resources ) throws Exception
297        {
298            boolean validation = line.hasOption( VALIDATE_OPTION );
299            if( validation )
300            {
301                System.setProperty( "project.validation.enabled", "true" );
302            }
303            URI uri = (URI) line.getValue( BUILDER_URI_OPTION, ANT_BUILDER_URI );
304            if( resources.length > 1 )
305            {
306                StringBuffer buffer = 
307                  new StringBuffer( "Initiating build sequence: (" + resources.length + ")\n" );
308                for( int i=0; i<resources.length; i++ )
309                {
310                    Resource resource = resources[i];
311                    buffer.append( "\n  (" + ( i+1 ) + ")\t" + resource.getResourcePath() );
312                }
313                buffer.append( "\n" );
314                getLogger().info( buffer.toString() );
315            }
316            
317            List list = line.getValues( TARGETS );
318            String[] targets = (String[]) list.toArray( new String[ list.size() ] );
319            for( int i=0; i<resources.length; i++ )
320            {
321                Resource resource = resources[i];
322                Part part = createPart( uri );
323                Builder builder = createBuilder( part );
324                boolean status = builder.build( resource, targets );
325                if( !status )
326                {
327                    return status;
328                }
329                System.gc();
330            }
331            return true;
332        }
333        
334       /**
335        * Build the supplied set of projects. If a build filure occurs then 
336        * abort the build sequence and exit.
337        *
338        * @param resources the sorted sequence of prouject to build
339        * @exception Exception if an error occurs
340        */
341        private void list( Resource[] resources ) throws Exception
342        {
343            if( resources.length == 1 )
344            {
345                Resource resource = resources[0];
346                if( resource instanceof Module )
347                {
348                    listModule( (Module) resource );
349                }
350                else
351                {
352                    listResource( resource );
353                }
354            }
355            else
356            {
357                listResources( resources );
358            }
359        }
360        
361        private void listModule( Module module ) throws Exception
362        {
363            print( "Listing module [" + module.getResourcePath() + "]\n"  );
364            listResource( "  ", module, 0 );
365            print( "" );
366        }
367        
368        private void listResource( Resource project ) throws Exception
369        {
370            print( "Listing project: " + project.getResourcePath() + "\n"  );
371            listResource( "  ", project, 0 );
372            print( "" );
373        }
374        
375        private void listResources( Resource[] resources ) throws Exception
376        {
377            print( "Selection: [" + resources.length + "]\n"  );
378            for( int i=0; i<resources.length; i++ )
379            {
380                Resource resource = resources[i];
381                String label = getLabel( i+1 );
382                print( label + resource );
383            }
384            print( "" );
385        }
386        
387    
388        private void listResource( String pad, Resource resource, int n ) throws Exception
389        {
390            if( n > 0 )
391            {
392                print( "\n[" + n + "] " + resource );
393            }
394            else
395            {
396                print( "\n" + resource );
397            }
398            print( "" );
399            print( pad + "version: " + resource.getVersion() );
400            print( pad + "basedir: " + resource.getBaseDir() );
401            String p = pad + "  ";
402            Type[] types = resource.getTypes();
403            if( types.length > 0 )
404            {
405                print( pad + "types: (" + types.length + ")" );
406                for( int i=0; i<types.length; i++ )
407                {
408                    print( p + types[i].getID() );
409                }
410            }
411    
412            Resource[] resources = resource.getProviders( Scope.BUILD, m_expand, true );
413            if( resources.length > 0 )
414            {
415                print( pad + "build phase providers: (" + resources.length + ")" );
416                for( int i=0; i<resources.length; i++ )
417                {
418                    Resource res = resources[i];
419                    print( p + res );
420                }
421            }
422            resources = resource.getProviders( Scope.RUNTIME, m_expand, true );
423            if( resources.length > 0 )
424            {
425                print( pad + "runtime providers: (" + resources.length + ")" );
426                for( int i=0; i<resources.length; i++ )
427                {
428                    Resource res = resources[i];
429                    print( p + res );
430                }
431            }
432            resources = resource.getProviders( Scope.TEST, m_expand, true );
433            if( resources.length > 0 )
434            {
435                print( pad + "test providers: (" + resources.length + ")" );
436                for( int i=0; i<resources.length; i++ )
437                {
438                    Resource res = resources[i];
439                    print( p + res );
440                }
441            }
442        }
443        
444        static void print( String message )
445        {
446            System.out.println( message );
447        }
448        
449        private static String getLabel( int n )
450        {
451            StringBuffer buffer = new StringBuffer();
452            buffer.append( "  [" + n );
453            buffer.append( "]" );
454            buffer.append( "        " );
455            String tag = buffer.toString();
456            return tag.substring( 0, 7 ) + " ";
457        }
458        
459       /**
460        * List general command help to the console.
461        * @exception IOException if an I/O error occurs
462        */
463        private void processHelp() throws IOException
464        {
465            HelpFormatter formatter = new HelpFormatter( 
466              HelpFormatter.DEFAULT_GUTTER_LEFT, 
467              HelpFormatter.DEFAULT_GUTTER_CENTER, 
468              HelpFormatter.DEFAULT_GUTTER_RIGHT, 
469              100 );
470            
471            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
472            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
473            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
474            
475            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
476            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
477            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
478            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
479            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
480            formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
481            
482            formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
483            formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
484            formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
485            formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED );
486            
487            formatter.setGroup( COMMAND_GROUP );
488            formatter.setShellCommand( "build" );
489            formatter.print();
490        }
491        
492        private Logger getLogger()
493        {
494            return m_logger;
495        }
496        
497        private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder();
498        private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder();
499        private static final GroupBuilder GROUP_BUILDER = new GroupBuilder();
500        private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder();
501        private static final PropertyOption PROPERTY_OPTION = new PropertyOption();
502        
503        private static final Option HELP_OPTION = 
504            OPTION_BUILDER
505              .withShortName( "help" )
506              .withShortName( "h" )
507              .withDescription( "List command help." )
508              .withRequired( false )
509              .create();
510        
511        private static final Option SELECT_OPTION = 
512            OPTION_BUILDER
513              .withShortName( "select" )
514              .withShortName( "s" )
515              .withDescription( "Build selected project(s)." )
516              .withRequired( false )
517              .withArgument(
518                ARGUMENT_BUILDER 
519                  .withDescription( "Project." )
520                  .withName( "pattern" )
521                  .withMinimum( 1 )
522                  .withMaximum( 1 )
523                  .create() )
524              .create();
525        
526        private static final Option VERBOSE_OPTION = 
527            OPTION_BUILDER
528              .withShortName( "verbose" )
529              .withShortName( "v" )
530              .withDescription( "Enable verbose mode." )
531              .withRequired( false )
532              .create();
533        
534        private static final Option LIST_OPTION = 
535            OPTION_BUILDER
536              .withShortName( "list" )
537              .withShortName( "l" )
538              .withDescription( "List selected project(s)." )
539              .withRequired( false )
540              .withArgument(
541                ARGUMENT_BUILDER 
542                  .withDescription( "Project." )
543                  .withName( "pattern" )
544                  .withMinimum( 0 )
545                  .withMaximum( 1 )
546                  .create() )
547              .create();
548        
549        private static final Option EXPAND_OPTION = 
550            OPTION_BUILDER
551              .withShortName( "expand" )
552              .withShortName( "e" )
553              .withDescription( "Expand dependencies." )
554              .withRequired( false )
555              .create();
556        
557        private static final Option CONSUMERS_OPTION = 
558            OPTION_BUILDER
559              .withShortName( "consumers" )
560              .withShortName( "c" )
561              .withDescription( "Consumer switch." )
562              .withRequired( false )
563              .create();
564        
565        private static final Option VALIDATE_OPTION = 
566            OPTION_BUILDER
567              .withShortName( "validate" )
568              .withDescription( "Enable deliverable validation." )
569              .withRequired( false )
570              .create();
571        
572        private static final Option VERSION_OPTION = 
573            OPTION_BUILDER
574              .withShortName( "version" )
575              .withDescription( "Build version." )
576              .withRequired( false )
577              .withArgument(
578                ARGUMENT_BUILDER 
579                  .withDescription( "Created artifact version." )
580                  .withName( "version" )
581                  .withMinimum( 1 )
582                  .withMaximum( 1 )
583                  .create() )
584              .create();
585    
586        private static final Option DECIMAL_OPTION = 
587            OPTION_BUILDER
588              .withShortName( "decimal" )
589              .withDescription( "Enable decimal versioning." )
590              .withRequired( false )
591              .create();
592        
593        private static final Option BUILDER_URI_OPTION = 
594            OPTION_BUILDER
595              .withShortName( "plugin" )
596              .withDescription( "Default builder plugin uri." )
597              .withRequired( false )
598              .withArgument(
599                ARGUMENT_BUILDER 
600                  .withDescription( "Artifact reference." )
601                  .withName( "artifact" )
602                  .withMinimum( 1 )
603                  .withMaximum( 1 )
604                  .withValidator( new URIValidator() )
605                  .create() )
606              .create();
607        
608        private static final Option TARGETS = 
609            ARGUMENT_BUILDER
610              .withName( "target" )
611              .create();
612    
613        private static final Group LIST_GROUP =
614          GROUP_BUILDER
615            .withMinimum( 0 )
616            .withOption( LIST_OPTION )
617            .withOption( EXPAND_OPTION )
618            .withOption( CONSUMERS_OPTION )
619            .create();
620        
621        private static final Group BUILD_GROUP =
622          GROUP_BUILDER
623            .withMinimum( 0 )
624            .withOption( SELECT_OPTION )
625            .withOption( CONSUMERS_OPTION )
626            .withOption( VERBOSE_OPTION )
627            .withOption( BUILDER_URI_OPTION )
628            .withOption( VERSION_OPTION )
629            .withOption( DECIMAL_OPTION )
630            .withOption( VALIDATE_OPTION )
631            .withOption( PROPERTY_OPTION )
632            .create();
633        
634        private static final Group COMMAND_GROUP =
635          GROUP_BUILDER
636            .withName( "options" )
637            .withOption( HELP_OPTION )
638            .withOption( LIST_GROUP )
639            .withOption( BUILD_GROUP )
640            .withOption( TARGETS )
641            .create();
642    
643        static
644        {
645            try
646            {
647                ANT_BUILDER_URI = new URI( "link:part:dpml/depot/dpml-tools-builder" );
648            }
649            catch( Exception e )
650            {
651                throw new RuntimeException( "will not happen", e );
652            }
653        }
654    
655    }
656